/*
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package hextd.server;

import com.twolattes.json.Json;
import com.twolattes.json.Json.Value;
import com.twolattes.json.Marshaller;
import com.twolattes.json.TwoLattes;
import com.usoog.tdcore.UserInfo;
import com.usoog.tdcore.map.MapInfo;
import com.usoog.tdcore.message.FactoryMessage;
import com.usoog.tdcore.message.GameInfo;
import com.usoog.tdcore.message.Message;
import com.usoog.tdcore.message.MessageError;
import com.usoog.tdcore.message.MessageFetch;
import com.usoog.tdcore.message.MessageGameList;
import com.usoog.tdcore.message.MessageMapData;
import com.usoog.tdcore.message.MessageMapList;
import com.usoog.tdcore.message.MessageReplay;
import com.usoog.tdcore.network.ConnectionListener;
import com.usoog.tdcore.network.NetworkConnection;
import com.usoog.tdcore.network.NetworkServer;
import com.usoog.tdcore.util.MD5;
import hextd.Constants;
import hextd.Constants.FetchType;
import hextd.HexTD;
import hextd.core.MapLoaderServer;
import java.io.BufferedReader;
import java.io.Console;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Server implements ConnectionListener {

	public static final String playerAuthRegExp = "([-0-9]+),([^\\n]+),([^\\n]+),([-0-9]+),([-0-9]+)";
	public static Pattern playerAuthPattern = Pattern.compile(playerAuthRegExp);
	//
	public static FactoryMessage messageFactory = new FactoryMessage();
	public static final String scorePostRegExpSp = "^postscore::Success:([0-9]+)$";
	public static Pattern scorePostPatternSp = Pattern.compile(scorePostRegExpSp);
	public static final String scorePostRegExpMp = "^postMpGame::Success:([0-9]+)$";
	public static Pattern scorePostPatternMp = Pattern.compile(scorePostRegExpMp);
	//
	private List<ServerUser> users;
	private List<ServerUser> usersNew;
	private List<String> userNames;
	private Map<Integer, ServerUser> usersByUserId;
	private Map<Integer, Channel> channels;
	private Channel lobby;
	private int nextChannelId = 0;
	private int nonAuthId = -10;
	//
	private int port = 4567;
	private URL authUrl, spPostUrl, mpPostUrl, mapListUrl, mapFetchUrl, replayFetchUrl;
	private NetworkServer serverCore;
	private MapLoaderServer mapLoader;
	//
	private List<MessageReplay> logsToCheck;
	private ReplayLogChecker logChecker;
	private Properties settings;
	//
	private PermanentStore permanentStore = new PermanentStore();
	private long cachValidity = 1l * 12 * 60 * 60 * 1000; // 12 hours
	private long cachCleanupPeriod = 1l * 30 * 60 * 1000; // 30 minutes
	private long channelResendPeriod = 1l * 30 * 1000; // 30 seconds
	private MD5 md5;
	private Random random = new Random();
	private Timer maintenanceTimer, countdownTimer;

	public Server(Properties config) {
		settings = config;

		port = new Integer(settings.getProperty("port"));
		try {
			authUrl = new URL(settings.getProperty("authUrl"));
		} catch (MalformedURLException ex) {
			System.err.println("Server: Bad url: " + ex.getMessage());
		}
		try {
			spPostUrl = new URL(settings.getProperty("spPostUrl"));
			mpPostUrl = new URL(settings.getProperty("mpPostUrl"));
			mapListUrl = new URL(settings.getProperty("mapListUrl"));
			mapFetchUrl = new URL(settings.getProperty("mapFetchUrl"));
			replayFetchUrl = new URL(settings.getProperty("replayFetchUrl"));
		} catch (MalformedURLException ex) {
			System.err.println("Server: Bad url: " + ex.getMessage());
		}
		System.out.println("Server:           port: " + port);
		System.out.println("Server:        authUrl: " + authUrl);
		System.out.println("Server:      spPostUrl: " + spPostUrl);
		System.out.println("Server:      mpPostUrl: " + mpPostUrl);
		System.out.println("Server:     mapListUrl: " + mapListUrl);
		System.out.println("Server:    mapFetchUrl: " + mapFetchUrl);
		System.out.println("Server: replayFetchUrl: " + replayFetchUrl);
		try {
			md5 = MD5.getInstance();
		} catch (NoSuchAlgorithmException ex) {
			Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
		}

		maintenanceTimer = new Timer("maintenanceTimer", true);
		countdownTimer = new Timer("countdownTimer", true);

		users = new ArrayList<ServerUser>();
		usersNew = new ArrayList<ServerUser>();
		userNames = new ArrayList<String>();
		usersByUserId = new HashMap<Integer, ServerUser>();
		channels = new HashMap<Integer, Channel>();
		Integer channelId = new Integer(this.nextChannelId++);
		lobby = new Channel(this, "The Lobby", channelId, countdownTimer);
		channels.put(channelId, lobby);
		mapLoader = new MapLoaderServer(messageFactory, mapListUrl, mapFetchUrl, "maps/levels.txt", getClass().getResource("/hextd/"));
		mapLoader.fetchRemoteIndex();

		logsToCheck = Collections.synchronizedList(new ArrayList<MessageReplay>());
		logChecker = new ReplayLogChecker(usersByUserId, logsToCheck, spPostUrl, mpPostUrl, mapLoader, maintenanceTimer);
		logChecker.startCheckTimer();
		TimerTask tokenCleaner = new TimerTask() {

			@Override
			public void run() {
				cleanupTokenCache();
				mapLoader.fetchRemoteIndex();
			}
		};
		maintenanceTimer.schedule(tokenCleaner, cachCleanupPeriod, cachCleanupPeriod);
		TimerTask channelListSender = new TimerTask() {

			@Override
			public void run() {
				sendChannelList();
			}
		};
		maintenanceTimer.schedule(channelListSender, channelResendPeriod, channelResendPeriod);

		readAll();
	}

	public void startServer() {
		serverCore = new NetworkServer(1000);
		serverCore.startServer(this, port);
	}

	public Map<Integer, Channel> getChannels() {
		return this.channels;
	}

	public UserInfo loadPlayerData(int userId) {
		UserInfo info = permanentStore.playerData.get("" + userId);
		if (info == null) {
			info = new UserInfo();
			info.userId = userId;
			permanentStore.playerData.put("" + userId, info);
		}
		return info;
	}

	private String createCachedToken(ServerUser user) {
		Date now = new Date();
		CacheEntry cacheEntry = new CacheEntry();
		cacheEntry.createTime = now.getTime();
		cacheEntry.userId = user.getUserInfo().userId;
		String hash = "A" + md5.hashData("" + random.nextInt() + " " + cacheEntry.hashCode());
		permanentStore.tokenCache.put(hash, cacheEntry);
		return hash;
	}

	private void cleanupTokenCache() {
		Entry<String, CacheEntry> entry;
		int removed = 0;
		int total = permanentStore.tokenCache.size();
		Date now = new Date();
		for (Iterator<Entry<String, CacheEntry>> entries = permanentStore.tokenCache.entrySet().iterator(); entries.hasNext();) {
			entry = entries.next();
			if (now.getTime() - entry.getValue().createTime > cachValidity) {
				entries.remove();
				removed++;
			}
		}
		System.out.println("Server::cleanupTokenCache: checked " + total + " tokens, removed " + removed + ".");
	}

	public boolean authenticatePlayer(ServerUser user, String token) {
		if (this.authUrl == null) {
			// We can not authenticate players, so every connection is a
			// new player
			nonAuthId--;
			UserInfo userInfo = new UserInfo();
			user.setUserInfo(userInfo);
			String name = userInfo.name;
			String newName = name;
			int i = 0;
			while (userNames.contains(newName)) {
				i++;
				newName = name + i;
			}
			userInfo.name = newName;
			userInfo.token = "A1";
			userInfo.userId = nonAuthId;
			userNames.add(newName);
			users.add(user);
			usersByUserId.put(userInfo.userId, user);
			usersNew.remove(user);
			System.out.println("Server::playerAuthenticated: Unsafe authenticated " + newName + " id=" + userInfo.userId);
			return true;
		} else {
			// We can authenticate players
			boolean success = false;
			String reason = "";
			if (token.equalsIgnoreCase("invalid")) {
				reason = " Not logged in.";
			} else if (token.length() == 33 && token.startsWith("A")) {
				CacheEntry ce = permanentStore.tokenCache.get(token);
				if (ce == null) {
					reason += "token not recognised.";
				} else {
					Date now = new Date();
					if (now.getTime() - ce.createTime < cachValidity) {
						UserInfo userInfo = loadPlayerData(ce.userId);
						user.setUserInfo(userInfo);
						System.out.println("Server::playerAuthenticated: internal auth: " + userInfo.userId + " " + userInfo.name + " " + userInfo.userRights);

						userNames.add(userInfo.name);
						users.add(user);
						usersNew.remove(user);
						usersByUserId.put(userInfo.userId, user);

						ce.createTime = now.getTime();
						permanentStore.tokenCache.put(token, ce);
						success = true;
					} else {
						reason += "token expired.";
						permanentStore.tokenCache.remove(token);
					}
				}
			} else {
				try {
					URLConnection connection = authUrl.openConnection();
					connection.setDoOutput(true);
					connection.setConnectTimeout(30000);
					OutputStream os = connection.getOutputStream();
					OutputStreamWriter out = new OutputStreamWriter(os);

					out.write(URLEncoder.encode("token", "UTF-8") + "=" + URLEncoder.encode(token, "UTF-8") + "&");
					out.close();
					os.close();
					StringBuilder result = new StringBuilder();

					InputStream is = connection.getInputStream();
					InputStreamReader isr = new InputStreamReader(is);
					BufferedReader in = new BufferedReader(isr);

					String receivedLine;
					while ((receivedLine = in.readLine()) != null) {
						result.append(receivedLine);
						Matcher matcher = playerAuthPattern.matcher(receivedLine);
						if (matcher.matches()) {
							try {
								int userId = new Integer(URLDecoder.decode(matcher.group(1), "UTF-8"));
								String userName = URLDecoder.decode(matcher.group(2), "UTF-8");
								String userRights = URLDecoder.decode(matcher.group(3), "UTF-8");
								int rank = new Integer(URLDecoder.decode(matcher.group(4), "UTF-8"));
								int games = new Integer(URLDecoder.decode(matcher.group(5), "UTF-8"));

								System.out.println("Server::playerAuthenticated: external auth: " + userId + " " + userName + " " + userRights + " " + rank + " " + games);

								UserInfo playerInfo = loadPlayerData(userId);
								user.setUserInfo(playerInfo);
								playerInfo.name = userName;
								playerInfo.userRights = userRights;
								playerInfo.rank = rank;
								playerInfo.games = games;
								playerInfo.token = createCachedToken(user);

								userNames.add(userName);
								users.add(user);
								usersNew.remove(user);
								usersByUserId.put(playerInfo.userId, user);
								success = true;
							} catch (NumberFormatException e) {
								System.out.println("Server::playerAuthenticated: Not a number in "
										+ "userId (" + matcher.group(1) + ") or "
										+ "rank (" + matcher.group(4) + ") or "
										+ "games (" + matcher.group(5) + ")");
							}
						}
					}
					in.close();
					isr.close();
					is.close();
					if (!success) {
						reason += "No valid response from auth server.\n"
								+ "Response was: " + result.toString();
					}
				} catch (IOException e) {
					reason += "Could not connect to auth server.";
				}
			}
			if (!success) {
				System.out.println("Server::playerAuthenticated: Auth failed for token '" + token + "' because: " + reason);
				user.sendLine(MessageError.formatMessage(Constants.ErrorType.AuthFailed.name(), reason));
			}

			return success;
		}
	}

	public void playerDisconnected(ServerUser user) {
		UserInfo userInfo = user.getUserInfo();
		if (userInfo != null) {
			System.out.println("Server::playerDisconnected: " + userInfo.name);
		}
		Channel channel = user.getChannel();
		if (channel != null) {
			channel.playerLeft(user, "nowhere (Disconnected)");
		}
		this.users.remove(user);
		this.usersNew.remove(user);
		if (userInfo != null) {
			this.userNames.remove(userInfo.name);
			this.usersByUserId.remove(userInfo.userId);
		}
	}

	public void playerToChannel(ServerUser player, Integer channelId) {
		if (channels.containsKey(channelId)) {
			Channel target = channels.get(channelId);
			Channel from = player.getChannel();
			if (target.isOpen()) {
				if (from != null) {
					from.playerLeft(player, target.getName());
				}
				target.playerJoined(player);
				if (target == lobby) {
					MessageGameList mgl = new MessageGameList(createGameInfoList());
					player.sendLine(mgl.getMessage());
				}
			}
		} else if (channelId.intValue() == -1) {
			Channel from = player.getChannel();
			if (from != null) {
				player.setChannel(null);
				from.playerLeft(player, "nowhere (Single Player)");
				System.out.println("Server::playerToChannel: " + player.getUserInfo().name + "-> null");
			}
		}
	}

	private List<GameInfo> createGameInfoList() {
		ArrayList<GameInfo> gameList = new ArrayList<GameInfo>();
		for (Channel c : channels.values()) {
			gameList.add(c.getGameInfo());
		}
		return gameList;
	}

	public void channelEmpty(Channel channel) {
		if (channel.getId() > 0) {
			channels.remove(channel.getId());
			MessageGameList mgl = new MessageGameList(createGameInfoList());
			lobby.sendLine(mgl.getMessage());
			System.out.println("Server::channelEmpty: removing channel " + channel.getId() + " " + channel.getName());
		}
	}

	public void createChannel(String name, ServerUser player) {
		Integer channelId;
		synchronized (this) {
			channelId = new Integer(this.nextChannelId++);
		}
		String finalName = name;

		System.out.println("Server::createChannel: creating channel " + channelId + " " + finalName);
		Channel newChannel = new Channel(this, finalName, channelId, countdownTimer);
		channels.put(channelId, newChannel);
		sendChannelList();
		playerToChannel(player, channelId);
	}

	private void sendChannelList() {
		MessageGameList mgl = new MessageGameList(createGameInfoList());
		lobby.sendLine(mgl.getMessage());
	}

	@Override
	public void connectionEstablished(NetworkConnection connection) {
		System.out.println("Server::connectionEstablished: New connection from " + connection.getRemoteAddress().toString());
		ServerUser newPlayer = new ServerUser(this);
		newPlayer.setConnection(connection);
		this.usersNew.add(newPlayer);
		MessageMapList mapList = mapLoader.getMapListMessage();
		if (mapList != null) {
			System.out.println("Server::connectionEstablished: Sending map list");
			newPlayer.sendLine(mapList.getMessage());
		}
	}

	@Override
	public void connectionFailed(NetworkConnection connection, String reason) {
		System.out.println("Server::connectionFailed: not handled by Server");
	}

	@Override
	public void connectionLost(NetworkConnection connection) {
		//System.out.println("Server::connectionLost: not handled by Server");
	}

	public int getAuthedPlayerCount() {
		return this.users.size();
	}

	public int getUnauthedPlayerCount() {
		return this.usersNew.size();
	}

	public int getChannelCount() {
		return this.channels.size();
	}

	public void addSpReplay(MessageReplay logMessage) {
		if (spPostUrl != null) {
			try {
				Map<String, String> options = logMessage.getOptions();
				URLConnection connection = spPostUrl.openConnection();
				connection.setDoOutput(true);
				OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());
				out.write(URLEncoder.encode("username", "UTF-8") + "=" + URLEncoder.encode(options.get("userName"), "UTF-8") + "&");
				out.write(URLEncoder.encode("userid", "UTF-8") + "=" + URLEncoder.encode(options.get("userId"), "UTF-8") + "&");
				out.write(URLEncoder.encode("level", "UTF-8") + "=" + URLEncoder.encode(options.get("level"), "UTF-8") + "&");
				out.write(URLEncoder.encode("score", "UTF-8") + "=" + URLEncoder.encode(options.get("score"), "UTF-8") + "&");
				out.write(URLEncoder.encode("mapId", "UTF-8") + "=" + URLEncoder.encode(options.get("mapId"), "UTF-8") + "&");
				out.write(URLEncoder.encode("replay", "UTF-8") + "=" + URLEncoder.encode(logMessage.getReplay(), "UTF-8") + "&");
				out.write(URLEncoder.encode("public", "UTF-8") + "=" + URLEncoder.encode(options.get("public"), "UTF-8") + "&");

				out.close();
				StringBuilder result = new StringBuilder();
				BufferedReader in = new BufferedReader(
						new InputStreamReader(
						connection.getInputStream()));

				String receivedLine;
				boolean success = false;
				while ((receivedLine = in.readLine()) != null) {
					result.append(receivedLine).append("\n");
					Matcher matcher = scorePostPatternSp.matcher(receivedLine);
					if (matcher.matches()) {
						String replayId = URLDecoder.decode(matcher.group(1), "UTF-8");
						System.out.println("Server::score posted: " + replayId);
						options.put("replayId", "" + replayId);
						if (Integer.parseInt(replayId) > 0) {
							success = true;
						}
					}
				}
				in.close();
				if (success) {
					logsToCheck.add(logMessage);
				} else {
					System.err.println("Server::addReplayLog: Failed to submit score!");
					System.err.println(result);
				}
			} catch (IOException ex) {
				System.err.println("Server::addReplayLog: IOException submitting score!");
			}
		} else {
			logsToCheck.add(logMessage);
		}
	}

	public void addMpReplay(MessageReplay log) {
		if (mpPostUrl != null) {
			try {
				Map<String, String> options = log.getOptions();
				URLConnection connection = mpPostUrl.openConnection();
				connection.setDoOutput(true);
				OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());
				out.write(URLEncoder.encode("replay", "UTF-8") + "=" + URLEncoder.encode(log.getReplay(), "UTF-8") + "&");
				out.write(URLEncoder.encode("public", "UTF-8") + "=" + options.get("public") + "&");
				out.write(URLEncoder.encode("ladder", "UTF-8") + "=" + options.get("ladder") + "&");
				out.write(URLEncoder.encode("mapId", "UTF-8") + "=" + options.get("mapId") + "&");

				out.close();
				StringBuilder result = new StringBuilder();
				BufferedReader in = new BufferedReader(
						new InputStreamReader(
						connection.getInputStream()));

				String receivedLine;
				boolean success = false;
				while ((receivedLine = in.readLine()) != null) {
					result.append(receivedLine).append("\n");
					Matcher matcher = scorePostPatternMp.matcher(receivedLine);
					if (matcher.matches()) {
						String replayId = URLDecoder.decode(matcher.group(1), "UTF-8");
						System.out.println("Server::score posted: " + replayId);
						options.put("replayId", replayId);
						if (Integer.parseInt(replayId) > 0) {
							success = true;
						}
					}
				}
				in.close();
				if (success) {
					logsToCheck.add(log);
				} else {
					System.err.println("Server::addReplayLog: Failed to submit score!");
					System.err.println(result);
				}
			} catch (IOException ex) {
				System.err.println("Server::addReplayLog: IOException submitting score!");
			}
		} else {
			logsToCheck.add(log);
		}
	}

	public MapLoaderServer getMapLoader() {
		return mapLoader;
	}

	public void fetchFromServer(MessageFetch mf, ServerUser from) {
		UserInfo playerInfo = from.getUserInfo();
		if (playerInfo == null) {
			playerInfo = new UserInfo();
			playerInfo.name = from.getConnection().getRemoteAddress().toString();
		}
		try {
			FetchType type = Constants.FetchType.valueOf(mf.getType());
			String playType;
			switch (type) {
				case maplist:
					MessageMapList message = mapLoader.getMapListMessage();
					System.out.println("Server::fetchFromServer: Sending maplist to " + playerInfo.name + " (" + playerInfo.userId + ")");
					from.sendLine(message.getMessage());
					return;
				case map:
					MapInfo info = mapLoader.getMap(mf.getId());
					System.out.println("Server::fetchFromServer: Sending map " + mf.getId() + " to " + playerInfo.name + " (" + playerInfo.userId + ")");
					MessageMapData mmd = new MessageMapData(-1, info.mapId, info.getContent());
					from.sendLine(mmd.getMessage());
					return;
				case mp:
					playType = "multi";
					break;
				case sp:
					playType = "single";
					break;
				default:
					System.err.println("Server::fetchFromServer: Unknown fetch action!");
					return;
			}

			System.err.println("Server::fetchFromServer: Fetching " + mf.getId() + " " + playType + " by " + from.getUserInfo().userId);

			URLConnection connection = replayFetchUrl.openConnection();
			connection.setDoOutput(true);
			OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());
			out.write(URLEncoder.encode("type", "UTF-8") + "=" + type + "&");
			out.write(URLEncoder.encode("replayId", "UTF-8") + "=" + mf.getId() + "&");
			out.write(URLEncoder.encode("requestingUserId", "UTF-8") + "=" + from.getUserInfo().userId + "&");
			out.close();


			StringBuilder result = new StringBuilder();
			BufferedReader in = new BufferedReader(
					new InputStreamReader(
					connection.getInputStream()));
			String receivedLine;
			Message replayMsg = null;
			while ((receivedLine = in.readLine()) != null) {
				result.append(receivedLine).append("\n");
				Message message = messageFactory.parseString(receivedLine);
				if (message.getKey().equals(MessageReplay.key)) {
					replayMsg = message;
				}
			}
			in.close();
			if (replayMsg != null) {
				System.err.println("Server::fetchReplay: Got Replay!");
				try {
					from.sendLine(replayMsg.getMessage());
				} catch (Exception ex) {
					Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
				}
			} else {
				System.err.println("Server::fetchReplay: Got no Replay!");
				System.err.println(result);
				from.sendLine(MessageError.formatMessage(Constants.ErrorType.ReplayLoadFailed.name(), "No reason known"));
			}
		} catch (IOException ex) {
			System.err.println("Server::fetchReplay: IOException submitting score!");
		}

	}

	public List<ServerUser> getPlayers() {
		return users;
	}

	public List<ServerUser> getPlayersNew() {
		return usersNew;
	}

	public void checkMaps() {
		//logChecker.startRegMap();
		mapLoader.fetchRemoteIndex();
	}

	public void stop() {
		serverCore.stopServer();
		serverCore.shutdown();
		writeAll();
	}

	public void writeAll() {
		FileOutputStream fos = null;
		{
			OutputStreamWriter out = null;
			try {
				Marshaller<PermanentStore> marshaller = TwoLattes.createMarshaller(PermanentStore.class);
				Value json = marshaller.marshall(permanentStore);
				File file = new File("HexTDstore.json");
				fos = new FileOutputStream(file);
				out = new OutputStreamWriter(fos, "UTF-8");
				out.write(json.toString());
				out.close();
				fos.close();
			} catch (UnsupportedEncodingException ex) {
				Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
			} catch (FileNotFoundException ex) {
				Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
			} catch (IOException ex) {
				Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
			} finally {
				try {
					if (fos != null) {
						fos.close();
					}
				} catch (IOException ex) {
					Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
				}
				try {
					if (out != null) {
						out.close();
					}
				} catch (IOException ex) {
					Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
				}
			}
		}
	}

	private void readAll() {
		FileInputStream fis = null;
		InputStreamReader in = null;
		try {
			fis = new FileInputStream("HexTDstore.json");
			in = new InputStreamReader(fis, "UTF-8");
			BufferedReader r = new BufferedReader(in);
			StringBuilder content = new StringBuilder();
			String line;
			while ((line = r.readLine()) != null) {
				content.append(line);
			}
			Marshaller<PermanentStore> marshaller = TwoLattes.createMarshaller(PermanentStore.class);
			try {
				Json.Value fromString = Json.fromString(content.toString());
				permanentStore = marshaller.unmarshall((Json.Object) fromString);
			} catch (ClassCastException e) {
				System.out.println("MapInfo::getMapData: Not a valid JSon map!");
			} catch (IllegalArgumentException e) {
				System.out.println("MapInfo::getMapData: Not a valid JSon map!");
			}
		} catch (UnsupportedEncodingException ex) {
			Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
		} catch (FileNotFoundException ex) {
			Logger.getLogger(Server.class.getName()).log(Level.INFO, "file not found: HexTDstore.json");
		} catch (IOException ex) {
			Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
		} finally {
			try {
				if (in != null) {
					in.close();
				}
			} catch (IOException ex) {
				Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
			}
		}
	}

	/**
	 * @param args the command line arguments
	 */
	public static void main(String[] args) {
		System.out.println("Version: " + HexTD.VERSION);

		Properties defaults = new Properties();
		defaults.setProperty("port", "4567");
		defaults.setProperty("authUrl", "");
		defaults.setProperty("spPostUrl", "");
		defaults.setProperty("mpPostUrl", "");
		defaults.setProperty("replayFetchUrl", "");
		defaults.setProperty("mapRegUrl", "");

		Properties config = new Properties(defaults);
		try {
			config.load(new FileInputStream("HexTD.conf"));
		} catch (FileNotFoundException ex) {
			try {
				System.out.println("Creating config file, " + defaults.size() + " keys");
				defaults.store(new FileWriter("HexTD.conf"), "Default settings.");
			} catch (IOException ex1) {
				Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex1);
			}
		} catch (IOException ex) {
			Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
		}

		Console c = System.console();
		if (c == null) {
			System.err.println("No console.");
			Server server = new Server(config);
			server.startServer();
		} else {
			Server server = new Server(config);
			server.startServer();

			boolean quit = false;
			do {
				String line = c.readLine(">> ");
				if (line.matches("^i$")) {
					System.out.println("Players Auth: " + server.getAuthedPlayerCount() + " Players notAuth: " + server.getUnauthedPlayerCount() + " Channels: " + server.getChannelCount());
					System.out.println("Unchecked logs: " + server.logsToCheck.size());
				}
				if (line.matches("^m$")) {
					server.checkMaps();
				}
				if (line.matches("^p$")) {
					System.out.println("Authenticated players:");
					for (ServerUser p : server.getPlayers()) {
						System.out.println(" * " + p.getUserInfo().name + " " + p.getConnection().getRemoteAddress() + " " + p.getChannel());
					}
					System.out.println("Unauthenticated players:");
					for (ServerUser p : server.getPlayersNew()) {

						System.out.println(" * " + p.getConnection().getRemoteAddress());
					}
				}
				quit = line.matches("^q(uit)?$");
			} while (!quit);

			server.stop();
			System.exit(0);
		}
	}
}
